//
// Created by 成飞 on 2024/1/11.
//
#include "meta_data.h"
#include "config/config_data.h"
#include <iostream>
#include <filesystem>
#include <set>

namespace meta{
    std::atomic<fid> readyFileNo;
    std::map<fid, ControlInfo> fileControlInfo;
    std::array<std::string_view, FILE_NO_MAX> buffers{};
    std::mutex mtx{};
    std::mutex mtx4range{};

    std::atomic_bool closeDB{false};

    std::string metaDirPathStr;
    std::string obsoleteFidLoggerPathStr;
    std::ofstream obsoleteFidLogger{};
    std::string fidAddrLoggerPathStr;
    std::ofstream fidAddrLogger{};

    std::string getLastLine(std::ifstream& file) {
        // 将文件指针移动到文件末尾
        file.seekg(0, std::ios::end);
        // 获取文件大小
        std::streampos fileSize = file.tellg();
        // 开始搜索最后一行的起始位置
        std::string lastLine;
        for (int i = 1; i <= fileSize; ++i) {
            file.seekg(-i, std::ios::end);
            char ch;
            file.get(ch);
            // 如果找到换行符，则说明找到了最后一行的起始位置
            if (ch == '\n') {
                std::getline(file, lastLine);
                break;
            }
        }
        file.seekg(0, std::ios::beg);
        return lastLine;
    }

    void loadLog(){
        std::ifstream addrLogger(fidAddrLoggerPathStr);
        if (!addrLogger.is_open()) {
            return;
        }
        repairMeta();
    }

    void init(){
        using namespace std;
        metaDirPathStr = conf::data.db.meta_path;
        filesystem::path metaDirPath(metaDirPathStr);
        create_directories(metaDirPath);
        obsoleteFidLoggerPathStr = metaDirPathStr + "/obsoleteFidLog.txt";
        fidAddrLoggerPathStr = metaDirPathStr + "/fidAddrLog.txt";
    }

    //store：「加载info或空」 写infotmp 删info 删log 改infotmp为info
    void storeInfo(){
        using namespace std;
        string metaInfoPathStr = metaDirPathStr + "/info.txt";
        string metaInfoTmpPathStr = metaDirPathStr + "/info_tmp.txt";
        ofstream outfile(metaInfoTmpPathStr);
        outfile << readyFileNo << endl;
        outfile << fileControlInfo.size() << endl;
        for (auto &it: fileControlInfo) {
            auto fid = it.first;
            auto &controlInfo = it.second;
            outfile << fid << endl;
            outfile << controlInfo.addr << endl;
            outfile << controlInfo.garbageSize << endl;
            outfile << controlInfo.invalidValOffsets.size() << endl;
            for (auto &i: controlInfo.invalidValOffsets) {
                outfile << i << endl;
            }
        }
        outfile << "finished" << endl;
        outfile.close();

        filesystem::remove(metaInfoPathStr);
        clearLog();
        filesystem::rename(metaInfoTmpPathStr, metaInfoPathStr);
    }

    void loadInfo(){
        using namespace std;
        string metaInfoPathStr = metaDirPathStr + "/info.txt";
        ifstream metaInfoFile(metaInfoPathStr);
        int tmp;
        metaInfoFile >> tmp;
        readyFileNo = tmp;
        int fciSize;
        metaInfoFile >> fciSize;
        for (int i = 0; i < fciSize; ++i) {
            meta::fid fid;
            metaInfoFile >> fid;
            fileControlInfo[fid];
            fileControlInfo[fid].readers = 0;
            metaInfoFile >> fileControlInfo[fid].addr;
            fileControlInfo[fid].fd =  fopen(fileControlInfo[fid].addr.c_str(), "w+");
            metaInfoFile >> fileControlInfo[fid].garbageSize;
            int invalidSize;
            metaInfoFile >> invalidSize;
            for (int j = 0; j < invalidSize; ++j) {
                f_off invalidOff;
                metaInfoFile >> invalidOff;
                fileControlInfo[fid].invalidValOffsets.push_back(invalidOff);
            }
        }
    }

    void load(){
        //store：「加载info或空」 写infotmp 删info 删log 改infotmp为info
        //可能情况：
        ////没info - 加载log
        ////info和infotmp无finished - 选择info + log + 删infotmp
        ////info和infotmp有finisehd - 清除info + 删log + 改infotmp为info + 选择info
        ////infotmp - 删log + 改infotmp为info + 选择info
        ////info - 选择info + log
        //
        //搞完之后store
        using namespace std;
        string metaInfoPathStr = metaDirPathStr + "/info.txt";
        string metaInfoTmpPathStr = metaDirPathStr + "/info_tmp.txt";
        ifstream metaInfoFile(metaInfoPathStr), metaInfoTmpFile(metaInfoTmpPathStr);
        if (!metaInfoFile.is_open() && !metaInfoTmpFile.is_open()){ //info不存在，这是初始加载 或者 上次崩溃了
            loadLog();
            return;
        }
        if (metaInfoFile.is_open() && !metaInfoTmpFile.is_open()) {
            loadInfo();
            loadLog();
            return;
        }
        if (!metaInfoFile.is_open() && metaInfoTmpFile.is_open()) {
            filesystem::remove(metaInfoPathStr);
            filesystem::rename(metaInfoTmpPathStr, metaInfoPathStr);
            loadInfo();
            return;
        }
        if (metaInfoFile.is_open() && metaInfoTmpFile.is_open()) {
            if (getLastLine(metaInfoTmpFile) == "finished") {
                filesystem::remove(metaInfoPathStr);
                clearLog();
                filesystem::rename(metaInfoTmpPathStr, metaInfoPathStr);
                loadInfo();
            }else{
                loadInfo();
                clearLog();
                filesystem::remove(metaInfoTmpPathStr);
            }
        }
    }

    void initLog(){
        fidAddrLogger = std::ofstream(fidAddrLoggerPathStr);
        obsoleteFidLogger = std::ofstream(obsoleteFidLoggerPathStr);
    }

    void closeLog(){
        obsoleteFidLogger.close();
        fidAddrLogger.close();
    }

    void clearLog(){
        std::filesystem::remove(obsoleteFidLoggerPathStr);
        std::filesystem::remove(fidAddrLoggerPathStr);
        for (const auto& entry : std::filesystem::directory_iterator(metaDirPathStr)) {
            if (entry.is_regular_file() && entry.path().filename().string().substr(0, 10) == "garbageLog") {
                std::filesystem::remove(entry.path());
            }
        }
    }

    //如果addrLog是空（说明两个Log都是空），则代表是初始加载或者上次从未persistence就崩溃了，直接返回。
    //addrLog不是空，则先修复addr，如果fidLog不是空，则再修复fid，获取到已经消灭了的fid的集合，然后按升序加载garbageLog（只看最后一行是finished的）
    //然后判断fidLog是否完整，不完整说明崩溃发生时正在gc，，则先gc最后一个脏的fid。
    //最后按升序加载buffer的logger
    void repairMeta(){
        using namespace std;
        //repair addr
        ifstream addrLogger(fidAddrLoggerPathStr);
        string fileNoStr,filePathStr,OK;
        while (addrLogger >> fileNoStr >> filePathStr >> OK) {
            if (OK == "ok") {
                fid fileNo = stoi(fileNoStr);
                fileControlInfo[fileNo].addr = filePathStr;
            }
        }

        //repair invalid fid
        set<fid> invalidFidSet;
        ifstream fidLogger(obsoleteFidLoggerPathStr);
        string readyFidStr, cleanFidStr,confirmFidStr, tmp; //readyToGC 192 cleanFileNo 786 192
        while (fidLogger >> tmp >> readyFidStr >> tmp >> cleanFidStr >> OK >> confirmFidStr) {
            int readyFid = stoi(readyFidStr);
            try {
                int confirmFid = stoi(confirmFidStr);
                if (readyFid == confirmFid) {
                    invalidFidSet.insert(confirmFid);
                }
            } catch (const std::invalid_argument& e) {
            }
        }

        //repair metadata
        set<string> garbageLoggerPathStrSet;
        for (const auto& entry : std::filesystem::directory_iterator(metaDirPathStr)) {
            if (entry.is_regular_file() && entry.path().filename().string().substr(0, 10) == "garbageLog") {
                garbageLoggerPathStrSet.insert(entry.path());
            }
        }
        for (auto &garbageLoggerPathStr: garbageLoggerPathStrSet) {
            ifstream garbageLogger(garbageLoggerPathStr);
            if (!garbageLogger.is_open()) {
                std::cerr << "Error opening file: " << garbageLoggerPathStr << std::endl;
                return;
            }
            if (getLastLine(garbageLogger) == "finished") {
                fid fileNo;
                f_off recordOffset;
                f_off recordLen;
                while (garbageLogger >> fileNo >> recordOffset >> recordLen) {
                    if (invalidFidSet.find(fileNo) != invalidFidSet.end()) {
                        continue;
                    }
                    meta::fileControlInfo[fileNo].garbageSize += recordLen;
                    meta::fileControlInfo[fileNo].invalidValOffsets.emplace_back(recordOffset);
                }
            }
        }
        storeInfo();
    }
}